package com.redis.sdk.lock;

import com.alibaba.fastjson.JSON;
import com.redis.sdk.RedisClusterUtils;
import com.redis.sdk.Spel;
import com.redis.sdk.cachecore.LockObject;
import com.redis.sdk.cachecore.OrderSort;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Order(OrderSort.REDIS_LOCK)
@Slf4j
public class LockAspect {

    private String prefix = "redisLock:%s";

    @Autowired
    private ILock redisLock;

    /**
     * 拦截指定注解
     */
    @Pointcut("@annotation(com.redis.sdk.lock.LockRedis)")
    public void lockRedis() {
    }

    @Around("lockRedis()")
    public Object lockRedis(ProceedingJoinPoint pjp) throws Throwable {


        log.info("开始分布式上锁！");
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        LockRedis lockRedis = method.getAnnotation(LockRedis.class);
        long expire = lockRedis.expire();
        long tryLock = lockRedis.tryLockTime();
        String[] key = lockRedis.value();
        boolean watchdog = lockRedis.watchdog();


        LockObject lockObject = getLockObject(key, method, pjp.getArgs(), tryLock, watchdog, expire);


        final Throwable[] throwableSource = new Throwable[1];
        Object result = redisLock.lock(lockObject, () -> {
            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                throwableSource[0] = throwable;
            }
            return null;
        });
        if (throwableSource[0] != null) {
            throw throwableSource[0];
        }
        return result;
    }

    private LockObject getLockObject(String[] key, Method method, Object[] args, long tryLock, boolean watchdog, long expire) {
        if (key.length == 0) {
            throw new RuntimeException("分布式锁key不能为空");
        }

        StringBuffer parseKey = new StringBuffer();
        for (String k :
                key) {
            parseKey.append(Spel.parseKey(k, method, args));
        }

        UUID uuid = UUID.randomUUID();

        LockObject lockObject = new LockObject(String.format(prefix, parseKey.toString()), uuid.toString(), tryLock);
        lockObject.setExpire(expire);
        lockObject.setTimeUnit(TimeUnit.SECONDS);

        if (watchdog) {
            // 看门狗固定30s过期时间
            lockObject.setExpire(30);
            lockObject.setWatchDog(Executors.newScheduledThreadPool(1));
        }

        return lockObject;
    }
}
